/*
 * ebcd.c - EBCD
 * Clunix, KyungWoon Cho, cezanne@clunix.com, 2006.1
 *
 * Copyright (c) 2001-2006 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "ebcd.h"
#include "peercon.h"

ebc_clid_t	ebcd_clid = EBC_CLID_NONE;
ebc_ndid_t	ebcd_ndid;
char		*ebcd_clname;
char		*ebcd_ndname;
ebc_addr_t	ebcd_myaddr;

ecm_uint16	ebcd_port;

char	*ebcd_bdcast_addr;
static ECMBOOL	help = ECM_FALSE;

static char	*logfile;

static ECMBOOL is_daemon;
ECMBOOL going_to_shutdown;

static char	*ebcd_clxdir;

ecm_workers_t	ebcd_workers;
ecm_workers_t	ebcd_core_workers;

static void
signal_handler(ecm_signo_t signo)
{
	switch (signo) {
	case ECM_SIGINT:
	case ECM_SIGTERM:
		ECMVERBOSE("Received termination signal");
		going_to_shutdown = ECM_TRUE;
		PCH_WAKEUP(ebcd_selection);
		break;
	default:
		break;
	}
}

static void
ebcd_usage(void)
{
	fprintf(stderr, "Usage: ebcd <options>\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "  -h: display this messages\n");
	fprintf(stderr, "  -D: run as daemon\n");
	fprintf(stderr, "  -p <ebc port>:   ebc port number\n");
	fprintf(stderr, "  -H <CLX_HOME>\n");
	fprintf(stderr, "  -b <broadcast addr>\n");
	fprintf(stderr, "  -c <cluster name>\n");
	fprintf(stderr, "  -t <timeout in msec>: aliveness timeout. 0 means no timeout\n");
	fprintf(stderr, "  -T <detection timeout in msec>: negative means that ebcd sends no detection packet\n");
	fprintf(stderr, "  -n <nd name>:    node name\n");
	fprintf(stderr, "  -l <log file>\n");
#ifdef ECM_DEBUG
	fprintf(stderr, "  -M <idmap file> ndid/mdid/ptid IDs mapping table\n");
	fprintf(stderr, "  -X: ebcd simple test(such as no detection delay..)\n");
	fprintf(stderr, "  -d <dbg set>    debug message\n");
	fprintf(stderr, "     a: debug all\n");
	fprintf(stderr, "     b: broadcast\n");
	fprintf(stderr, "     c: ebcd general debug\n");
	fprintf(stderr, "     P: dpch\n");
	fprintf(stderr, "     p: PCH\n");
	fprintf(stderr, "     r: route\n");
	fprintf(stderr, "     l: aliveness checking\n");
	fprintf(stderr, "     e: external connection\n");
	fprintf(stderr, "     d: packet drop\n");
	fprintf(stderr, "  -C <ebc dbg set>    debug message\n");
#endif
}

#ifdef ECM_DEBUG

ECMBOOL		ebcd_simple_test;
ecm_dbg_t	ebcd_dbg;

static ECMBOOL
setup_debugset(const char *args)
{
	ecm_uint32	ebcd_dbgset = 0;

	while (*args) {
		switch (*args) {
		case 'a':
			ebcd_dbgset |= DBG_ALL;
			break;
		case 'c':
			ebcd_dbgset |= DBG_EBCD;
			break;
		case 'b':
			ebcd_dbgset |= DBG_BDCAST;
			break;
		case 'p':
			ebcd_dbgset |= DBG_PCH;
			break;
		case 'P':
			ebcd_dbgset |= DBG_DPCH;
			break;
		case 'r':
			ebcd_dbgset |= DBG_ROUTE;
			break;
		case 'l':
			ebcd_dbgset |= DBG_ALIVECHECK;
			break;
		case 'e':
			ebcd_dbgset |= DBG_EXTCONN;
			break;
		case 'd':
			ebcd_dbgset |= DBG_PKTDROP;
			break;
		default:
			return ECM_FALSE;
		}
		args++;
	}
	ecm_set_debugset(ebcd_dbg, ebcd_dbgset);
	return ECM_TRUE;
}

#endif

static void
setup_clxdir(const char *clxdir)
{
	if (ebcd_clxdir != NULL)
		return;
	if (ecm_empty_str(clxdir))
		return;
	ebcd_clxdir = ecm_strdup(clxdir);
	ecm_setenv(ECM_CLX_HOME_ENV, ebcd_clxdir);
	ecm_set_default_clxdir(ebcd_clxdir);
}

static void
setup_env(void)
{
	const char	*e;

	setup_clxdir(ecm_getenv(ECM_CLX_HOME_ENV));

	if (ebcd_port == 0)
		ecm_sscanf(ecm_getenv(ECM_EBC_PORT_ENV), "%hu", &ebcd_port);

	if (ecm_empty_str(ebcd_ndname) && (e = ecm_getenv("EBCD_ND_NAME")))
		ebcd_ndname = ecm_strdup(e);
}

static ECMBOOL
parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hH:Dc:t:T:b:l:p:n:d:C:M:Xv")) != -1) {
		switch (c) {
		case 'h':
			help = ECM_TRUE;
			break;
		case 'D':
			is_daemon = ECM_TRUE;
			break;
		case 'p':
			if (ebcd_port == 0 && ecm_sscanf(ecm_optarg, "%hu", &ebcd_port) != 1)
				return ECM_FALSE;
			break;
		case 'b':
			if (ebcd_bdcast_addr == NULL)
				ebcd_bdcast_addr = ecm_strdup(ecm_optarg);
			break;
		case 'c':
			if (ebcd_clname == NULL)
				ebcd_clname = ecm_strdup(ecm_optarg);
			break;
		case 'n':
			if (ebcd_ndname == NULL)
				ebcd_ndname = ecm_strdup(ecm_optarg);
			break;
		case 't':
			if (ebcd_alivecheck_timeout == 0 &&
			    ecm_sscanf(ecm_optarg, "%d", &ebcd_alivecheck_timeout) != 1)
				return ECM_FALSE;
			break;
		case 'T':
			if (ebcd_detection_interval == 0 &&
			    ecm_sscanf(ecm_optarg, "%d", &ebcd_detection_interval) != 1)
				return ECM_FALSE;
			break;
		case 'l':
			if (logfile == NULL)
				logfile = ecm_strdup(ecm_optarg);
			break;
#ifdef ECM_DEBUG
		case 'd':
			if (!setup_debugset(ecm_optarg))
				return ECM_FALSE;
			break;
		case 'C':
			if (!ebc_set_debugset_str(ecm_optarg))
				return ECM_FALSE;
			break;
		case 'M':
			ebc_dbg_setup_idmap(ecm_optarg);
			break;
		case 'X':
			ebcd_simple_test = ECM_TRUE;
			break;
#endif
		case 'H':
			setup_clxdir(ecm_optarg);
			break;
		case 'v':
			ecm_set_verbose_mode(ECM_TRUE);
			break;
		default:
			return ECM_FALSE;
		}
	}
	return ECM_TRUE;
}

static void
parse_args_from_cmdline(const char *c_cmdline)
{
	char	*cmdline;
	int	argc;
	char	**argvs;

	if (ecm_empty_str(c_cmdline))
		return;
	cmdline = ecm_strdup(c_cmdline);
	argc = ecm_get_argvs_from_cmdline(cmdline, &argvs);
	if (argc > 0) {
		ecm_getopt_clear();
		parse_args(argc, argvs);
	}
	ecm_free(argvs);
	ecm_free(cmdline);
}

static void
load_conf(void)
{
	sr_t	*sr_conf, *sr_ebcd_conf;
	char	*confpath;

	confpath = ecm_gen_path(ebcd_clxdir, "etc", "econf", NULL);
	if (!eds_read_sr_conf(confpath, &sr_conf)) {
		ecm_free(confpath);
		return;
	}
	ecm_free(confpath);

	sr_ebcd_conf = sr_getc(sr_conf, "ebcd");

	parse_args_from_cmdline(sr_getc_str(sr_ebcd_conf, "arg"));

	if (ebcd_port == 0) {
		ebcd_port = sr_getc_int(sr_conf, "ebc_port");
		if (ebcd_port == 0)
			ebcd_port = EBC_PORT_DEF;
	}

	if (ecm_empty_str(ebcd_clname))
		ebcd_clname = ecm_strdup(sr_getc_str(sr_ebcd_conf, "clname"));
	if (ecm_empty_str(ebcd_ndname))
		ebcd_ndname = ecm_strdup(sr_getc_str(sr_ebcd_conf, "ndname"));
	if (ebcd_detection_interval == 0) {
		ebcd_detection_interval = sr_getc_int(sr_ebcd_conf, "detection_interval");
	}

	if (ecm_empty_str(ebcd_bdcast_addr))
		ebcd_bdcast_addr = ecm_strdup(sr_getc_str(sr_ebcd_conf, "bdcast_addr"));
	sr_free(sr_conf);
}

#ifdef _WIN32
void
ebcd_stop(ecm_ptr_t ctx)
{
	going_to_shutdown = ECM_TRUE;
	PCH_WAKEUP(ebcd_selection);
}
#endif

static void
setup_logfile(void)
{
	FILE	*logfp = NULL;

	if (ecm_strcmp(logfile, ".") == 0) {
		char	*logpath;

		logpath = ecm_gen_rpath(NULL, "log/ebcd.log");
		logfp = ecm_fopen(logpath, "a+");
		ecm_free(logpath);
	}
	else if (!ecm_empty_str(logfile))
		logfp = ecm_fopen(logfile, "a+");
	if (logfp == NULL)
		logfp = stderr;
	ecm_set_output_file(logfp);
}

int
main(int argc, char *argv[], char *envs[])
{
	ecm_daemon_check("ebcd", "ECM ebcd", "Netman", main, argc, argv);
#ifdef _WIN32
	ecm_daemon_reg_stopper(ebcd_stop, NULL);
#endif
	ecm_initialize();
	ecm_set_process_priority(1);

#ifdef ECM_DEBUG
	ebcd_dbg = ecm_create_dbg();
#endif

	ebc_set_listen_backlog(100);

	ebc_initialize();

	ecm_signal(ECM_SIGINT, signal_handler);
	ecm_signal(ECM_SIGTERM, signal_handler);

	if (!parse_args(argc, argv)) {
		ebcd_usage();
		return 1;
	}

	setup_env();

	load_conf();

	setup_logfile();

	if (ebcd_clname == NULL || ebcd_ndname == NULL) {
		ECMERR("cluster and node name required");
		return 1;
	}
	ebc_get_clid(ebcd_clname, &ebcd_clid);

	if (help) {
		ebcd_usage();
		return 0;
	}

	if (is_daemon)
		ecm_daemonize(ebcd_clxdir, "ebcd");

	ECMVERBOSE("ebcd started");
	ECMVERBOSE("cluster: %s, self node: %s", ebcd_clname, ebcd_ndname);

	ebcd_initialize_monitor();
	ebcd_initialize_peercon();

	if (!ebcd_initialize_pchs()) {
		if (is_daemon)
			ecm_daemon_exit(2, "ebcd startup failed! Another ebcd already run ??");
		else {
			ECMERR("ebcd startup failed! Another ebcd already run ??");
		}
		return 2;
	}

	ebcd_initialize_mch();
	ebcd_initialize_route();
	ebcd_workers = ecm_init_workers(20, NULL, NULL, NULL);
	ebcd_core_workers = ecm_init_workers(10, NULL, NULL, NULL);

	ecm_daemon_set_running();

	ebcd_app_loop();

	ebcd_finalize_pchs();
	ebcd_finalize_peercon();

	ecm_daemon_cleanup();

	ECMVERBOSE("ebcd ended");
	return 0;
}

#ifdef ECM_DEBUG

/* This is just a dummy for sr_fancy_print() linkage */
void
dummy_sr_fancy_print(void)
{
	sr_fancy_print(NULL, NULL, 2, "");
}

#endif
